using LuaInterface;
using System;
using System.Collections.Generic;
using System.Text;
using virtuaPol.Util;

namespace virtuaPol.Model
{
    public enum CheckCycleUnit
    {
        Seconds,
        Minutes,
        Hours,
        Days,
        Months,
        Years
    }

    public enum EventType
    {
        Silent, // Checked in the backgroud by the worker thread
        User    // Executed by a user action
    }

    public struct RegisterObjectsStruct
    {
        public string Name { get; set; }
        public object Target { get; set; }

        public RegisterObjectsStruct(string name, object target) : this()
        {
            Name = name;
            Target = target;
        }
    }

    [System.Xml.Serialization.XmlType("event")]
    public class CEvent : INamedObject
    {
        public const string SCRIPT_TEMPLATE =
              "-- Event script: \n"
            + "-- Author: Name <name@email.com>\n--\n"
            + "-- You can access the world object through 'world' table\n\n"
            + "-- bool doSimulation()\n"
            + "-- Check if the event should be triggered.\n"
            + "function doSimulation()\n"
            + "\t\n"
            + "\treturn false\n"
            + "end\n\n"
            + "-- void doEvent()\n"
            + "-- Trigger the event and manipulate the world object accordingly.\n"
            + "function doEvent()\n"
            + "\t\n"
            + "end\n\n"
            + "-- bool initTest()\n"
            + "-- Initialize world object for running a test.\n-- The world userdata contains an empty world object.\n"
            + "-- Returns true if the test run should trigger the event, false otherwise.\n"
            + "function initTest()\n"
            + "\t-- luanet.load_assembly(\"vpModel\") -- Load the model namespace\n"
            + "\t-- Country = luanet.import_type(\"virtuaPol.Model.CCountry\") -- Load CCountry type\n"
            + "\t-- Person = luanet.import_type(\"virtuaPol.Model.CPerson\") -- Load CPerson type\n"
            + "\t-- Property = luanet.import_type(\"virtuaPol.Model.CProperty\") -- Load CProperty type\n"
            + "\t-- PropertyType = luanet.import_type(\"virtuaPol.Model.CpropertyValueTypes\") -- Load Property Type Enum\n"
            + "\t\n"
            + "\treturn false\n"
            + "end\n\n"
            + "-- bool checkTestResult()\n"
            + "-- Check if the test was successful. I.e if the world object was modified properly.\n"
            + "function checkTestResult()\n"
            + "\t\n"
            + "\treturn true\n"
            + "end\n";


        // Locker object for thread safety
        private object _locker = new object();

        // Set to min for the first check to be active
        private DateTime nextCycle = DateTime.MinValue;

        private string name;
        [System.Xml.Serialization.XmlAttribute("name")]
        public string Name
        {
            get { lock (_locker) { return name; } }
            set { lock (_locker) { name = value; } }
        }

        private string description;
        [System.Xml.Serialization.XmlElement("description")]
        public string Description
        {
            get { lock (_locker) { return description; } }
            set { lock (_locker) { description = value; } }
        }

        private EventType type;
        [System.Xml.Serialization.XmlAttribute("type")]
        public EventType Type
        {
            get { lock (_locker) { return type; } }
            set { lock (_locker) { type = value; } }
        }

        private int check_cycle;
        [System.Xml.Serialization.XmlAttribute("check-cycle")]
        public int CheckCycle
        {
            get { lock (_locker) { return check_cycle; } }
            set { lock (_locker) { check_cycle = value; } }
        }

        private CheckCycleUnit check_cycle_unit;
        [System.Xml.Serialization.XmlAttribute("check-cycle-unit")]
        public CheckCycleUnit CheckCycleUnit
        {
            get { lock (_locker) { return check_cycle_unit; } }
            set { lock (_locker) { check_cycle_unit = value; } }
        }

        private string script;
        [System.Xml.Serialization.XmlElement("script")]
        public string Script
        {
            get { lock (_locker) { return script; } }
            set { lock (_locker) { script = value; } }
        }

        public CEvent()
        { }

        public CEvent(string name)
        {
            Name = name;
            Type = EventType.Silent;
            Script = SCRIPT_TEMPLATE;
        }

        public bool Simulate(CWorld world, IList<RegisterObjectsStruct> registerObjects)
        {
            // check for cycle
            if (!checkCycle()) return false;

            bool result = false;
            Lua lua = new Lua();
            try
            {
                lua.DebugHook += new EventHandler<DebugHookEventArgs>(lua_DebugHook);
                lua.DoString(script, name);
                // Expose world object to Lua script
                lua["world"] = world;

                if (registerObjects != null)
                {
                    foreach (RegisterObjectsStruct obj in registerObjects)
                    {
                        lua[obj.Name] = obj.Target;
                    }
                }

                // Simulate the event
                object[] res = lua.GetFunction("doSimulation").Call(new object[] { });
                result = (bool)res[0];
            }
            catch (Exception ex)
            {
                CLogger.LogError(ex);
            }
            return result;
        }

        public bool Execute(CWorld world, bool check, IList<RegisterObjectsStruct> registerObjects)
        {
            // Check for cycle
            if (check && !checkCycle()) return false;

            Lua lua = new Lua();
            try
            {
                lua.HookException += new EventHandler<HookExceptionEventArgs>(lua_HookException);
                lua.DebugHook += new EventHandler<DebugHookEventArgs>(lua_DebugHook);
                lua.DoString(script, name);
                // Expose world object to Lua script
                lua["world"] = world;
                lua.NewTable("game");

                if (registerObjects != null)
                {
                    foreach (RegisterObjectsStruct obj in registerObjects)
                    {
                        lua[obj.Name] = obj.Target;
                    }
                }


                // Execute the event
                if (check)
                {
                    LuaFunction sim = lua.GetFunction("doSimulation");
                    if (sim == null) return false;
                    object[] res = sim.Call(new object[] { });
                    if (!(bool)res[0])
                        return false;
                }
                lua.GetFunction("doEvent").Call(new object[] { });
                
                // Update next cycle time
                calcNextCycle();

                return true;
            }
            catch (Exception ex)
            {
                CLogger.LogError(ex);
                return false;
            }
        }

        void lua_HookException(object sender, HookExceptionEventArgs e)
        {
            CLogger.LogError(e.Exception);
        }

        void lua_DebugHook(object sender, DebugHookEventArgs e)
        {
            LuaDebug d = e.LuaDebug;
            CLogger.WriteLine(
                String.Format("[Lua] ERROR: {1} (in eventscript {2} on line {3})",
                d.what, d.name, d.currentline));
        }

        private void calcNextCycle()
        {
            DateTime lastCheck = DateTime.Now;
            switch (CheckCycleUnit)
            {
                case CheckCycleUnit.Seconds:
                default:
                    nextCycle = lastCheck.AddSeconds((double)CheckCycle);
                    break;
                case CheckCycleUnit.Minutes:
                    nextCycle = lastCheck.AddMinutes((double)CheckCycle);
                    break;
                case CheckCycleUnit.Hours:
                    nextCycle = lastCheck.AddHours((double)CheckCycle);
                    break;
                case CheckCycleUnit.Days:
                    nextCycle = lastCheck.AddDays((double)CheckCycle);
                    break;
                case CheckCycleUnit.Months:
                    nextCycle = lastCheck.AddMonths(CheckCycle);
                    break;
                case CheckCycleUnit.Years:
                    nextCycle = lastCheck.AddYears(CheckCycle);
                    break;
            }
        }

        private bool checkCycle()
        {
            int res = DateTime.Now.CompareTo(nextCycle);
            return res >= 0;
        }
    }
}
